package testApplets;

import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;

import marchingCubes.CAADMCube;
import processing.core.PApplet;
import processing.core.PGraphics;
import processing.core.PGraphics3D;
import processing.core.PMatrix3D;
import processing.opengl.*;
import simpleGeom.Mesh;
import simpleGeom.MeshEdge;
import simpleGeom.Vec2D;
import simpleGeom.Vec3D;

public class VoxelShapeApplet extends PApplet {
	//dimensions of voxelgrid
	int nX =64;
	int nY = 64;
	int nZ = 32;
	//200x100x50

	// precalculated values
	int nYZ=nY*nZ;
	int nXYZ=nY*nZ*nX;

	// declaration and construction of the voxelgrid
	float[] voxels = new float[nXYZ];
	float[] voxelsSmall = new float[nXYZ];
	int[]colorTypes=new int[nXYZ];
	int[]colorValues=new int[nXYZ];
	
	
	float iso=0.5f;

	CAADMCube mCube=new CAADMCube();
	boolean doExport;
	Slider[]settings;
	Button[]buttons;
	int setting=0;
	public void setup() {
	  size(1000, 700, OPENGL);
	 
	  for (int i=0;i<nXYZ;i++) {
		    colorTypes[i]=(int)random(4);
		  }
	  settings=new Slider[9*3+1];
	  buttons=new Button[9*3+1];
	  float sliderHeight=height/(settings.length+10);
	
	  float cY=0;
	  for (int i=0;i<settings.length;i++){
		 
		  
		  Slider slider=new Slider(30,(int)cY,170,(int)sliderHeight);
		  Button button=new Button(0,(int)cY,20,(int)sliderHeight);
		  settings[i]=slider;
		  buttons[i]=button;
		  cY+=sliderHeight;
		  if (i==0||i%3==0)cY+=sliderHeight;
	  }
	  buttons[13].pressed=true;
	  buttons[19].pressed=true;
	  buttons[25].pressed=true;
	  
	  settings[10].value=0.5f;
	  settings[11].value=0.25f;
	  settings[12].value=0.5f;
	  
	  settings[13].value=0.45f;
	  settings[14].value=0.15f;
	  settings[15].value=0.15f;
	  
	  settings[16].value=0.3f;
	  settings[17].value=0.25f;
	  settings[18].value=0.9f;
	  
	  settings[19].value=0.5f;
	  settings[20].value=0.25f;
	  settings[21].value=0.5f;
	  
	  settings[22].value=0.95f;
	  settings[23].value=0.49f;
	  settings[24].value=0.75f;
	  
	  settings[25].value=0.6f;
	  settings[26].value=0.15f;
	  settings[27].value=0.8f;
	  println("set Values");
	  setValues();
	  println("values setted");
	}
	float phase=2*PI;
	public float smallScale(float x,float y,float z){
		float vx1=settings[1].getRelValue();
		float vx2=settings[2].getRelValue();
		float vx3=settings[3].getRelValue();
		
		float vy1=settings[4].getRelValue();
		float vy2=settings[5].getRelValue();
		float vy3=settings[6].getRelValue();
		
		float vz1=settings[7].getRelValue();
		float vz2=settings[8].getRelValue();
		float vz3=settings[9].getRelValue();
		
		
		float vX=sin(vx1*1f*(x)*PI  + vx2*phase);
		if (buttons[1].pressed)vX=crop(vX);
		float vY=sin(vy1*1f*(y)*PI  +vy2*phase);
		if (buttons[4].pressed)vY=crop(vY);
		float vZ=sin(vz1*1f*(z)*PI +vz2*phase );
		if (buttons[7].pressed)vZ=crop(vZ);
		return vX*vx3+vY*vy3+vZ*vz3;
		/*float value=-1;
		value=max(vX*vx3,value);
		value=max(vY*vy3,value);
		value=max(vZ*vz3,value);
		return value;*/
	}
	
	
	public float mediumScale(float x,float y,float z){
		
		float vx1=settings[10].getRelValue();
		float vx2=settings[11].getRelValue();
		float vx3=settings[12].getRelValue();
		
		float vy1=settings[13].getRelValue();
		float vy2=settings[14].getRelValue();
		float vy3=settings[15].getRelValue();
		
		float vz1=settings[16].getRelValue();
		float vz2=settings[17].getRelValue();
		float vz3=settings[18].getRelValue();

		float vX=sin(vx1*16f*x*PI/nX+vx2*phase);
		if (buttons[10].pressed)vX=crop(vX);
		float vY=sin(vy1*16f*y*PI/nY+vy2*phase);
		if (buttons[13].pressed)vY=crop(vY);
		float vZ=sin(vz1*4*z*PI/nZ+vz2*phase);
		if (buttons[16].pressed)vZ=crop(vZ);
		return vX*vx3+vY*vy3+vZ*vz3;
	}
	public  float bigScale(float x,float y,float z){
		float vx1 = settings[19].getRelValue();
		float vx2 = settings[20].getRelValue();
		float vx3 = settings[21].getRelValue();

		float vy1 = settings[22].getRelValue();
		float vy2 = settings[23].getRelValue();
		float vy3 = settings[24].getRelValue();

		float vz1 = settings[25].getRelValue();
		float vz2 = settings[26].getRelValue();
		float vz3 = settings[27].getRelValue();
		float value = 0;

		float vX = sin(vx1 * 8f * x * PI / nX + vx2 * phase);
		if (buttons[19].pressed)
			vX = crop(vX);
		float vY = sin(vy1 * 2f * y * PI / nY + vy2 * phase);
		if (buttons[22].pressed)
			vY = crop(vY);
		float vZ = sin(vz1 * 8f * z * PI / nZ + vz2 * phase);
		if (buttons[27].pressed)
			vZ = crop(vZ);
		return vX * vx3 + vY * vy3 + vZ * vz3;  
	}
	
	
	public void setValues() {
		  for (int x=0;x<nX;x++) {
		    for (int y=0;y<nY;y++) {
		      for (int z=0;z<nZ;z++) {
		        int i=getIndex(x, y, z);
		        float value=0;
		       // value=(float)Math.pow(sin(5f*x*PI/nX+3),3)*1f;
		       // 
		        //value+=pow(sin(2f*y/nY  + 1),mouseY/height) * 2f;
		        value+=bigScale(x,y,z);
		        value+=mediumScale(x,y,z)*1f;
		        
		        voxels[i]=value;
		      }
		    }
		  }
		  float iso1=0.5f;
		  normalize(voxels);
		 
		for (int x = 0; x < nX; x++) {
			for (int y = 0; y < nY; y++) {
				for (int z = 0; z < nZ; z++) {
					int i = getIndex(x, y, z);

					// value=(float)Math.pow(sin(5f*x*PI/nX+3),3)*1f;
					//
					// value+=pow(sin(2f*y/nY + 1),mouseY/height) * 2f;
					// value+=bigScale(x,y,z);
					// value+=mediumScale(x,y,z)*1f;
					// float value=smallScale(x,y,z);
					
					voxelsSmall[i]=smallScale(x, y, z);
					
				}
			}
		}
		//normalize(voxelsSmall);
		for (int i=0;i<voxelsSmall.length;i++){
				if (voxels[i]<iso){
					if (voxelsSmall[i]<0){}
					//voxels[i]+=voxelsSmall[i];
				}
				//else voxels[i]=iso+0.1f;
			
		}
		/*for (int i=0;i<voxelsSmall.length;i++){
			if (voxels[i]<iso){
				voxels[i]+=voxelsSmall[i]*iso1;
			}
		}*/
		  doBorders();
		 // addIso();
	}

	public void setValues3() {
	  for (int x=0;x<nX;x++) {
	    for (int y=0;y<nY;y++) {
	      for (int z=0;z<nZ;z++) {
	        int i=getIndex(x, y, z);
	       // float value=(float)Math.pow(sin(5f*x*PI/nX),3)*1f;
	       // value=(float)Math.pow(sin(5f*x*PI/nX+3),3)*1f;
	       // value+=sin(6.7f*x*PI/nX+2.5f);
	        //value+=pow(sin(2f*y/nY  + 1),mouseY/height) * 2f;
	       
	     float value=0;
	        value+=pow(sin(4f*(x)*PI/nX+HALF_PI),3)*3f;
	        value+=pow(sin(2f*(y)*PI/nY  + 5.4f),3) * 3f;
	        value+=pow(sin(2f*(z)*PI/nZ  + 3.4f),3) * 1f;
	        
	        value+=pow( sin(8f*(x)*PI/nX+HALF_PI),3)*3;
	        value+=pow(sin(5f*(y)*PI/nY  + 5.4f) ,3)*3;
	        value+=pow(sin(4f*(z)*PI/nZ  + 3.4f+x*0.4f) ,3)* 2f;
	        
	         value+=crop(sin(24f*(x)*PI/nX+HALF_PI  )) * 2f;
	        value+=crop(sin(15f*(y)*PI/nY  + 2.4f)) * 1.2f;
	        value+=crop(sin(8f*(z)*PI/nZ  + 3.4f)) * 	2f;
	        
	        
	       /* value+=sin(20f*(x)*PI/nX  + 1.4f) * 0.2f;
	        value+=sin(30f*(y)*PI/nY  + 5.4f) * 0.2f;
	        value+=sin(15f*(z)*PI/nZ  + 3.4f) * 0.2f;
	        
	        value+=sin(40f*(x)*PI/nX  + 1.4f) * 0.5f;
	        value+=sin(60f*(y)*PI/nY  + 5.4f) * 0.5f;
	        value+=sin(30f*(z)*PI/nZ  + 3.4f) * 0.5f;*7
	        */
	        value+=crop(sin(50f*(x)*PI/nX  + 1.4f)) * 0.3f;
	        value+=crop(sin(100f*(y)*PI/nY  + 5.4f)) * 0.3f;
	        value+=crop(sin(20f*(z)*PI/nZ  + 3.4f)) * 0.3f;
	      
	        //value+=addPolar(x,y,z,20,20,20)*1f;
	        //value+=addPolar(x,y,z,nX-30,70,0)*1f;
	       
	        //value+=sin(2.8f*z*PI/nZ  + 2.4f) * 1f;

	        //value+=cos(20*(x+z)*PI/(nZ+nX)+5);
	       // value+=sin(sqrt((x-5)*(x-5)+(y-20)*(y-20)+(z-13)*(z-13))/10f);
	        value+=y*1f/nY;
	        voxels[i]=value;
	        
	      }
	    }
	  }
	  normalize(voxels);
	
	 // addIso();
	  cropOutterShape();
	  doBorders();
	}
	public float crop(float v,float border){
		//return v;
		if (v>border)return 1;
		
		else return -1;
	}
	public float crop(float v){
		return crop(v,0);
	}
	public void setValues2() {
		  for (int x=0;x<nX;x++) {
		    for (int y=0;y<nY;y++) {
		      for (int z=0;z<nZ;z++) {
		        int i=getIndex(x, y, z);
		        float value=(float)Math.pow(sin(5f*x*PI/nX),3)*1f;
		        value=(float)Math.pow(sin(5f*x*PI/nX+3),3)*1f;
		       // value+=sin(6.7f*x*PI/nX+2.5f);
		        //value+=pow(sin(2f*y/nY  + 1),mouseY/height) * 2f;
		        value+=abs(sin(2.8f*y*PI/nY  + 1.4f) * 1f);
		        value+=abs(sin(2.8f*y*PI/nY  + 4.4f) * 1f);
		       // value+=abs(sin((20f)*y*PI/nY+1))*0.2f;
		        value=0;
		        value+=pow(sin(2f*(z)*PI/nZ  + 3.4f),3) * 1f;
		        value+=sin(6f*(x+y)*PI/nX  + 1.4f) * 1f;
		        value+=sin(2f*(y+z)*PI/nY  + 5.4f) * 1f;
		        
		        value+=sin(4f*z*PI/nZ  + 3.4f) * 0.5f;
		        value+=sin(12f*x*PI/nX  + 1.4f) * 0.5f;
		        value+=sin(4f*y*PI/nY  + 5.4f) * 0.5f;
		        
		        
		        value+=sin(8f*z*PI/nZ  + 3.4f) * 0.5f%0.2f;
		        value+=sin(24f*x*PI/nX  + 1.4f) * 0.5f%0.1f;
		        value+=sin(8f*y*PI/nY  + 5.4f) * 0.5f%0.15f;
		        
		        value+=sin(16f*z*PI/nZ  + 3.4f) * 2;
		        value+=abs(sin(30f*x*PI/nX  + 1.4f) * 2);
		        value+=sin(14f*y*PI/nY  + 5.4f) * 2f;
		        
		        //value+=addPolar(x,y,z,20,20,20)*2f;
		        //value+=addPolar(x,y,z,nX-30,70,0)*2f;
		       
		        //value+=sin(2.8f*z*PI/nZ  + 2.4f) * 1f;

		        //value+=cos(20*(x+z)*PI/(nZ+nX)+5);
		       // value+=sin(sqrt((x-5)*(x-5)+(y-20)*(y-20)+(z-13)*(z-13))/10f);
		        value+=y*1f/nY;
		        voxels[i]=value;
		        
		      }
		    }
		  }
		  //cropOutterShape();
		}
	
	public void addIso(){
		 normalize(voxels);
		  // now we modify the isovalues again
		  for (int i=0;i<voxels.length;i++){
			  voxels[i]= abs(sin(9f*voxels[i]  + 1));
			 
		  }  
	}
	public void doBorders(){
		  normalize(voxels);
		  // borders
		  for (int x=0;x<nX;x++) {
		    for (int y=0;y<nY;y++) {
		      for (int z=0;z<nZ;z++) {
		        if (x==0||x==nX-1||y==0||y==nY-1||z==0||z==nZ-1){
		          int i=getIndex(x, y, z);
		          voxels[i]=1;
		        }
		      }
		    }
		  }
	}
	
	public float addPolar(float x1,float y1,float z1,float px,float py,float pz){
		float dX=x1-px;
        float dY=y1-py;
        float dZ=z1-pz;
        float angle=Vec2D.getAngle(dX, dY);
        float angle2=Vec2D.getAngle(dX, dZ);
        float dist=Vec3D.dist(px, py,pz, x1, y1,z1);
        float value=0;
        value+=cos(5*(angle+dist/nX));
        value+=sin(3f*(angle2+dist/nX));
       //sin(10f*angle2)
       // value+=+sin((dist/nX))*1f;
        return value;
	}
	public void cropOutterShape(){
		float zHalf=nZ*0.5f;
		for (int x=0;x<nX;x++) {
		    for (int y=0;y<nY;y++) {
		      for (int z=0;z<nZ;z++) {
		    	  int i=getIndex(x, y, z);
		    	 float value=(sin(5f*x*PI/nX  + 1.4f)+1)*0.2f+0.5f;
		    	  value+=(sin(5f*y*PI/nY  + 1.4f)+1)*0.2f+0.5f;
		    	 float d=abs(value-zHalf)*1f/zHalf;
		    	 
		    	 float zV=sin(4*x*PI/nX)*zHalf;
		    	 if (z<zHalf){};
		    	 //if (d<0){
		    	  voxels[i]+=d*0.5f;
		    	// }
		      }
		  }
	  }
	}
	float rotation=0;
	public Mesh createMesh(){
		Mesh mesh=new Mesh();
		for (int i=0;i<mCube.xc.length;i++){
			mesh.addNode(mCube.xc[i], mCube.yc[i], mCube.zc[i]);
		}
		for (int i=0;i<mCube.triangles.length;i+=3){
			mesh.addTri(mCube.triangles[i],mCube.triangles[i+1],mCube.triangles[i+2]);
		}
		return mesh;
	}
	
	public void draw() {
	
		
		
			 setValues();
		
		
		
	 //normalize(voxels);
	 //iso=8.5f;
	 mCube.marchingCubesMinim(nX,nY,nZ,voxels,iso,1,0,0,0,null);
	 if (doExport) {
	    doExport = false;
	    mCube.saveObj(this.selectOutput());
	  }
	 hint(ENABLE_DEPTH_TEST);
	  // setup of scene
	  background(0);
	  camera(0, 0,-nX*2f, 0, 0, 0, 0, 1, 0);
	
	  scale(1.5f);
	  noStroke();
	  pointLight(200,200,220,nX, nY*0.5f, -nZ*0.5f);
	  pointLight(200,220,200,-nX*0.5f, nY, -nZ*0.5f);
	  pointLight(200,220,200,10, -nY, -nZ*0.5f);
	 rotateY(rotation);
	  //lights();
	
	  translate(-nX*0.5f, -nY*0.5f, -nZ*0.5f);
	  iso=settings[0].getRelValue();

	  iso=max(0.01f,iso);
	  iso=min(0.99f,iso);

	 // stroke(255,0,0);
	  Mesh mesh=createMesh();
	  //mesh.display3D(g);
	  Mesh dual=mesh.getDual();
	  beginShape(QUADS);
	  for (int i=0;i<dual.getNEdges();i++){
		  MeshEdge edge=dual.getEdge(i);
		  //edge.display3D(g);
		  vertex(edge.getX1(),edge.getY1(),edge.getZ1());
		  vertex(edge.getX2(),edge.getY2(),edge.getZ2());
		  vertex(edge.getX2(),edge.getY2(),edge.getZ2()+1);
		  vertex(edge.getX1(),edge.getY1(),edge.getZ1()+1);
	  }
	  endShape();
	  //noFill();
	 /* beginShape(TRIANGLES);
	  if (mCube.triangles.length>0&&mCube.xc.length>0){
	    for (int i=0;i<mCube.triangles.length;i++){
	      int vI=mCube.triangles[i];
	      vertex(mCube.xc[vI],mCube.yc[vI],mCube.zc[vI]);
	    }
	  }
	  endShape();*/
	  hint(DISABLE_DEPTH_TEST);
	  PGraphics3D p3d = ((PGraphics3D) g);
	  PMatrix3D currCameraMatrix = new PMatrix3D(p3d.camera);
	  camera();
	  int dimX=200;
			
			//cp5.draw();
			float dValue=height*1f/settings.length;
			for (int i=0;i<settings.length;i++){
				Slider slider=settings[i];
				
				slider.display(g);
				Button button=buttons[i];
					button.display(g);	
				
			}
		p3d.camera = currCameraMatrix;
	  //drawAsQuads();
	}


	public void normalize(float[] values) {
	  float maxV = Float.NEGATIVE_INFINITY;
	  float minV = Float.POSITIVE_INFINITY;
	  for (int i = 0; i < values.length; i++) {
	    float v = values[i];
	    maxV = max(maxV, v);
	    minV = min(minV, v);
	  }
	  float dX = maxV - minV;
	  for (int i = 0; i < values.length; i++) {
	    float v = values[i];
	    values[i] = (v - minV) / dX;
	  }
	}
public void mousePressed(){
	if (this.mousePressed&&mouseX<200){
		for (int i=0;i<settings.length;i++){
			Slider slider=settings[i];
			slider.setValue(mouseX, mouseY);
		}
		for (int i=0;i<buttons.length;i++){
			Button button=buttons[i];
			button.setValue(mouseX, mouseY);
		}
		
		}
}
	// gui
	public void keyPressed() {
	  if (key == 'e')
	    doExport = true;
	  if (key == 'r')
		    rotation+=PI*0.25f;
	}

	public int getIndex(int x, int y, int z) {
		  return x*nY*nZ+y*nZ+z;
		}
		public int getX(int i) {
		  return i/(nY*nZ);
		}
		public int getY(int i) {
		  return (i/nZ)%nY;
		}
		public int getZ(int i) {
		  return i%nZ;
		}
	
		public void drawAsQuads(){
			  // looping to draw the voxels
			  beginShape(QUADS);
			  for (int x=0;x<nX-1;x++) {
			    for (int y=0;y<nY-1;y++) {
			      for (int z=0;z<nZ-1;z++) {
			        int i=getIndex(x, y, z);
			        //  draw a face between voxel and z neighbour
			        if (voxels[i]<iso!=voxels[i+1]<iso) {
			          drawZQuad(x, y, z+0.5f);
			        }
			        //  draw a face between voxel and y neighbour
			        if (voxels[i]<iso!=voxels[i+nZ]<iso) {
			          drawYQuad(x, y+0.5f, z);
			        }
			        //  draw a face between voxel and x neighbour
			        if (voxels[i]<iso!=voxels[i+nYZ]<iso) {
			          float xM=x+0.5f;
			          drawXQuad(x+0.5f, y, z);
			        }
			      }
			    }
			  }
			  endShape();
			}

			void drawZQuad(float x,float y,float z){
			  vertex(x-0.5f,y-0.5f,z);
			  vertex(x-0.5f,y+0.5f,z);
			  vertex(x+0.5f,y+0.5f,z);
			  vertex(x+0.5f,y-0.5f,z);
			}
			void drawYQuad(float x,float y,float z){
			  vertex(x-0.5f,y,z-0.5f);
			  vertex(x-0.5f,y,z+0.5f);
			  vertex(x+0.5f,y,z+0.5f);
			  vertex(x+0.5f,y,z-0.5f);
			}
			void drawXQuad(float x,float y,float z){
			  vertex(x,y-0.5f,z-0.5f);
			  vertex(x,y-0.5f,z+0.5f);
			  vertex(x,y+0.5f,z+0.5f);
			  vertex(x,y+0.5f,z-0.5f);
			}
			class Wavefuntion{
				Slider[] sliders;
				Button[] buttons;
				float fAmp;
				float fFreq;
				float fOffset;
				boolean bCrop;
				public void update(int mx,int my){
					for (int i=0;i<sliders.length;i++){
						sliders[i].setValue(mx, my);
					}
					for (int i=0;i<buttons.length;i++){
						buttons[i].setValue(mx, my);
					}
				}
				float getValue(float v){
					float value= sin(fAmp*v+fOffset);
					if (bCrop)value+=crop(value);
					return value*=fAmp;
					
				}
				public void display(){
					for (int i=0;i<sliders.length;i++){
						sliders[i].display(g);
					}
				}
			}
			class GuiRect{
				int x=0;
				int width=200;
				int y=0;
				int height=0;
				GuiRect(int x,int y,int width,int height){
					this.x=x;
					this.y=y;
					this.width=width;
					this.height=height;
				}
				public boolean contains(int mx,int my){
					if (mx>x&&mx<x+width&&my>y&&my<y+height)return true;
					return false;
				}
				public void display(PGraphics g){
					g.pushStyle();
					g.fill(0);
					g.stroke(255);
					g.rectMode(CORNER);
					g.rect(x,y,width,height);
					g.popStyle();
				}
			}
			class Button extends GuiRect{
				public boolean pressed;
				public void setValue(int mx,int my){
					if (contains(mx,my))pressed=!pressed;
				}
				public Button(int x,int y,int width,int height){
					super(x,y,width,height);
				}
				public void display(PGraphics g){
					super.display(g);
					if (pressed){
					g.pushStyle();
					g.fill(255,0,0);
					g.rect(x,y,width,height);
					g.popStyle();
					}
				}
			}
			class Slider extends GuiRect{
				
				
				float value=0.5f;
				Slider(int x,int y,int width,int height){
					super(x,y,width,height);
				}
				public float getRelValue(){
					return value;
				}
				public void display(PGraphics g){
					super.display(g);
					g.pushStyle();
					g.fill(255,0,0);
					g.rect(x,y,value*width,height);
					g.popStyle();
				}
				public void setValue(int mx,int my){
					if (contains(mx,my)){
						value=((mx-x)*1f/width);
					}
				}
				
			}
}
